Creative Commons License

1 Background

Describe data here.

The following packages are needed for this report

library(tidyverse)
library(limma)
library(QFeatures)
library(msqrob2)

2 Input parameters copied from msqrob2gui

Initialize all input parameters used in the GUI.

2.1 Input tab

featuresFile <- "featuresFile.txt"
annotationFile <- "annotationFile.xlsx"

Specification of feature file and annotation file. Note that the feature file and the annotation file are copied to the zip file for the report and are named featuresFile.txt and annotationFile.xlsx, respectively.

2.2 Preprocessing tab

groupBy <- "Proteins"
logTrans <- TRUE
removeRazor <- TRUE
filterColumns <- c("Reverse", "Potential.contaminant")
minObsFeat <- 2L
normMethod <- "center.median"
  • The features are grouped by the variable groupBy=Proteins.
  • Log transformation: logTrans=TRUE.
  • Remove razor peptides: removeRazor=TRUE
  • Following variables are used for filtering: filterColumns= Reverse, Potential.contaminant
  • Peptides that are picked up in less than minObsFeat=2 are removed
  • The features are normalized using normMethod=center.median

2.3 Summarization tab

sumMethod <- "robust"

The features are summarized according to the variable groupBy=Proteins using summarization method: sumMethod=robust.

2.4 Model tab

form <- "~treatment*time"
doRidge <- FALSE
  • The data are modelled using the following formula: form=~treatment*time

  • The parameters are estimated using robust ridge regression if the argument doRidge equals TRUE, otherwise robust regresion without ridge penalization. Here, doRidge=FALSE

2.5 Inference tab

contrast <- "treatmentLN+treatmentLN:time2w=0"
sigLevel <- 0.05
  • The statistical test assesses the null hypothesis specified using the following contrast for each summarized features: treatmentLN+treatmentLN:time2w=0.
  • Summarized features are returned as significant using the \(\alpha=0.05\) FDR-level.

2.6 Report Tab

maxPlot <- 10L

In the report the maxPlot=10 most significant features will be plotted in detail-plots.

3 Data import

We first import the data from FeaturesFile.txt file. This is the file containing your raw Feature-level intensities.

To import the data we use the QFeatures package.

ecols <- grep("Intensity\\.", names(read.delim(featuresFile)))

pe <- readQFeatures(
    table = featuresFile, fnames = 1, ecol = ecols,
    name = "rawFeatures", sep = "\t"
)
currentAssay <- "rawFeatures"
pe
## An instance of class QFeatures containing 1 assays:
##  [1] rawFeatures: SummarizedExperiment with 30575 rows and 30 columns

In the following code chunk, we set-up the colData with the information on the design.

  • The file path to the annotation data is annotationFile.xlsx
exp_annotation <- as.data.frame(unclass(openxlsx::read.xlsx(annotationFile)))

runName <- which(
  vapply(exp_annotation, function(x) return(
    identical(
      sort(
        as.character(
          colnames(pe[[1]]))
        ),
      sort(
        as.character(x))
      )
    ), FUN.VALUE = TRUE))
if (length(runName)>0) runName <- runName[1]
rownames(exp_annotation) <- as.character(
  exp_annotation[,runName]
  )

exp_annotation <- exp_annotation[colnames(pe[[1]]),]

for (j in colnames(exp_annotation))
  colData(pe)[[j]] <- exp_annotation[,j]

4 Preprocessing

This section preforms preprocessing for the feature. This include

  • log transformation,
  • filtering and
  • summarization of the data.

4.1 Log-transformation

We calculate how many non zero intensities we have per feature and this will be useful for filtering.

rowData(pe[[currentAssay]])$nNonZero <- rowSums(assay(pe[[currentAssay]]) > 0)

features with zero intensities are missing features and should be represent with a NA value rather than 0.

pe <- zeroIsNA(pe, currentAssay) # convert 0 to NA

52% of all feature intensities are missing and for some features we do not even measure a signal in any sample.

If the variable logTrans is TRUE than the logtransformation is performed.

logTrans
## [1] TRUE
if(logTrans) {
  pe <- logTransform(pe, base = 2, i = currentAssay, name = "logFeatures")
  currentAssay <- "logFeatures"
} 

4.2 Filtering

  1. Handling razor features

In maxQuant output a peptide can map to multiple proteins, as long as there is none of these proteins present in a smaller subgroup.

if removeRazor is TRUE. Razor features are removed.

removeRazor
## [1] TRUE
if (removeRazor) pe[[currentAssay]] <-
 pe[[currentAssay]][rowData(pe[[currentAssay]])[,groupBy]
 %in% smallestUniqueGroups(rowData(pe[[currentAssay]])[,groupBy]),]
  1. Filter

The variable filterColumns contains the columns that will be used to filter the data. This currently only works for Maxquant input. For Maxquant values that are indicated with a “+” are filtered, e.g. Decoys (in the column named Reverse) and contaminants (in the columns named Contaminants or Potential.contaminants).

filterColumns
## [1] "Reverse"               "Potential.contaminant"
if (length(filterColumns)>0)
  for (j in filterColumns)
  {
      rowData(pe[[currentAssay]])[is.na(rowData(pe[[currentAssay]])[,j]),j] <-""
      pe[[currentAssay]] <- pe[[currentAssay]][rowData(pe[[currentAssay]])[,j] != "+", ]
  }
  1. Drop features that were only identified in less than 2 samples
minObsFeat
## [1] 2
pe[[currentAssay]] <- pe[[currentAssay]][rowData(pe[[currentAssay]])$nNonZero >= minObsFeat, ]
nrow(pe[[currentAssay]])
## [1] 23690

We keep 23690 features upon filtering.

4.3 Normalize the data

The data are normalized using the center.median method (If normMethod is “none” no normalisation is performed).

normMethod
## [1] "center.median"
if (normMethod != "none")
{
  pe <- normalize(pe, 
                i = currentAssay, 
                name = "normFeatures", 
                method = normMethod)
  currentAssay <- "normFeatures"
}

4.4 Explore normalized data

  pe[[currentAssay]] %>% 
    assay %>%
    as.data.frame() %>%
    gather(sample, intensity) %>% 
  ggplot(aes(x = intensity, group = sample, color = sample)) +
    geom_density()
## Warning: Removed 315064 rows containing non-finite values (stat_density).

We can visualize our data using a Multi Dimensional Scaling plot, eg. as provided by the limma package.

pe[[currentAssay]] %>% 
  assay %>%
  limma::plotMDS() 

The first axis in the plot is showing the leading log fold changes (differences on the log scale) between the samples.

4.5 Summarization

The data are summarized using the summarization method: robust (if sumMethod is “none” no summarization is performed).

sumMethod
## [1] "robust"
if (sumMethod!="none") 
{
  if (sumMethod=="robust") fun <- MsCoreUtils::robustSummary
  if (sumMethod=="sum") fun <- base::colSums
  if (sumMethod=="mean") fun <- base::colMeans
  if (sumMethod=="median") fun <- matrixStats::colMedians
  if (sumMethod=="medpolish") fun <- MsCoreUtils::medianPolish

  pe <- aggregateFeatures(pe,
  i = currentAssay,
  fcol = groupBy,
  na.rm = TRUE,
  name = "sumFeatures",
  fun = fun
  )
  currentAssay <- "sumFeatures"
}
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.

An MDS plot of the summarized features can be found below.

 plotMDS(assay(pe[[currentAssay]]))

5 Data Analysis

5.1 Estimation

We model the summarized feature level expression values using msqrob. By default msqrob2 estimates the model parameters using robust regression.

We will model the data using following formula

as.formula(form)
## ~treatment * time
pe <- msqrob(object = pe, i = currentAssay, formula = as.formula(form), ridge = doRidge)

5.2 Inference

We can also explore the design of the model that we specified using the the package ExploreModelMatrix

library(ExploreModelMatrix)
visDesign <- VisualizeDesign(colData(pe),form)
visDesign$plotlist
## [[1]]

The following contrast will be assessed for each summarized feature:

contrast
## [1] "treatmentLN+treatmentLN:time2w=0"
L <- makeContrast(
  contrast, 
  parameterNames = colnames(visDesign[[3]])
  )
pe <- hypothesisTest(object = pe, i = currentAssay, contrast = L)

The following features are significant at the 0.05 FDR-level.

rowData(pe[[currentAssay]])[,colnames(L)] %>% 
  arrange(pval) %>% 
  filter(adjPval < sigLevel) %>% 
  DT::datatable() %>% 
  DT::formatSignif(columns = 1:6,digits=3)

5.3 Plots

5.3.1 Volcano-plot

The FDR is controlled at the 0.05.

sigLevel
## [1] 0.05
volcano <- ggplot(rowData(pe[[currentAssay]])[, colnames(L)],
                  aes(x = logFC, y = -log10(pval), color = adjPval < sigLevel)) +
  geom_point(cex = 2.5) +
  scale_color_manual(values = alpha(c("black", "red"), 0.5)) + theme_minimal()
volcano

5.3.2 Heatmap

We first select the names of the summarized features that were declared signficant at the FDR-level of 0.05. If we could reject the null hypothesis related to the specified contrast for more than 1 summarized feature a heatmap will be made for the significant features.

sigLevel
## [1] 0.05
sigNames <- rowData(pe[[currentAssay]])[,colnames(L)] %>%
  rownames_to_column("feature") %>%
  arrange(pval) %>% 
  filter(adjPval<sigLevel) %>%
  pull(feature)
if (length(sigNames) >1) heatmap(assay(pe[[currentAssay]])[sigNames, ]) else cat("No plots are generated because there are no significant summarized features at the", sigLevel, "FDR level")

5.3.3 Detail plots

We first extract the normalized rawFeatures expression values for a particular summarized feature. You selected maxPlot=10 so detail plots are constructed for the 10 most significant summarized features that are DA at the specified FDR level of 0.05. Note, that you can increase maxPlot to generate more plots.

maxPlot
## [1] 10
if (length(sigNames) > maxPlot) 
  plotNames <- sigNames[1:maxPlot] else 
    plotNames <- sigNames
if ("normFeatures" %in% names(pe) & "sumFeatures" %in% names(pe) & length(plotNames) >= 1) for (protName in plotNames)
{
pePlot <- pe[protName, , c("normFeatures","sumFeatures")]
pePlotDf <- data.frame(longFormat(pePlot))
pePlotDf$assay <- factor(pePlotDf$assay,
                        levels = c("normFeatures","sumFeatures"))
# plotting
p1 <- ggplot(
    data = pePlotDf,
    aes(x = colname, y = value, group = rowname)
    ) +
  geom_line() + 
  geom_point() +  
  theme(axis.text.x = element_text(angle = 70, hjust = 1, vjust = 0.5)) +
  facet_grid(~assay) + 
  ggtitle(protName) 
print(p1)

# plotting 2
p2 <- ggplot(
    pePlotDf, 
    aes(x = colname, y = value)) +
  geom_boxplot(outlier.shape = NA) + 
  geom_point(
    position = position_jitter(width = .1),
    aes(shape = rowname)) +
  scale_shape_manual(values = 1:nrow(pePlotDf)) +
  labs(title = protName, x = "sample", y = "feature intensity (log2)") +
  theme(axis.text.x = element_text(angle = 70, hjust = 1, vjust = 0.5)) +
  facet_grid(~assay) +
  ggtitle(protName) 
print(p2)
} else cat("No plots are generated because there are no significant summarized features at the", sigLevel, "FDR level")

6 Session Info

With respect to reproducibility, it is highly recommended to include a session info in your script so that readers of your output can see your particular setup of R.

sessionInfo()
## R version 4.1.1 (2021-08-10)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Big Sur 10.16
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] nl_BE.UTF-8/nl_BE.UTF-8/nl_BE.UTF-8/C/nl_BE.UTF-8/nl_BE.UTF-8
## 
## attached base packages:
## [1] parallel  stats4    stats     graphics  grDevices utils     datasets 
## [8] methods   base     
## 
## other attached packages:
##  [1] ExploreModelMatrix_1.4.0    msqrob2_1.0.0              
##  [3] QFeatures_1.2.0             MultiAssayExperiment_1.18.0
##  [5] SummarizedExperiment_1.22.0 Biobase_2.52.0             
##  [7] GenomicRanges_1.44.0        GenomeInfoDb_1.28.4        
##  [9] IRanges_2.26.0              S4Vectors_0.30.0           
## [11] BiocGenerics_0.38.0         MatrixGenerics_1.4.3       
## [13] matrixStats_0.61.0          limma_3.48.3               
## [15] forcats_0.5.1               stringr_1.4.0              
## [17] dplyr_1.0.7                 purrr_0.3.4                
## [19] readr_2.0.1                 tidyr_1.1.3                
## [21] tibble_3.1.4                ggplot2_3.3.5              
## [23] tidyverse_1.3.1            
## 
## loaded via a namespace (and not attached):
##  [1] minqa_1.2.4             colorspace_2.0-2        ellipsis_0.3.2         
##  [4] XVector_0.32.0          fs_1.5.0                clue_0.3-59            
##  [7] rstudioapi_0.13         farver_2.1.0            DT_0.19                
## [10] fansi_0.5.0             lubridate_1.7.10        xml2_1.3.2             
## [13] codetools_0.2-18        splines_4.1.1           knitr_1.34             
## [16] jsonlite_1.7.2          nloptr_1.2.2.2          broom_0.7.9            
## [19] cluster_2.1.2           dbplyr_2.1.1            shinydashboard_0.7.1   
## [22] shiny_1.7.0             compiler_4.1.1          httr_1.4.2             
## [25] backports_1.2.1         assertthat_0.2.1        Matrix_1.3-4           
## [28] fastmap_1.1.0           lazyeval_0.2.2          cli_3.0.1              
## [31] later_1.3.0             htmltools_0.5.2         tools_4.1.1            
## [34] gtable_0.3.0            glue_1.4.2              GenomeInfoDbData_1.2.6 
## [37] Rcpp_1.0.7              cellranger_1.1.0        jquerylib_0.1.4        
## [40] vctrs_0.3.8             nlme_3.1-153            crosstalk_1.1.1        
## [43] rintrojs_0.3.0          xfun_0.26               openxlsx_4.2.4         
## [46] lme4_1.1-27.1           rvest_1.0.1             mime_0.11              
## [49] lifecycle_1.0.0         zlibbioc_1.38.0         MASS_7.3-54            
## [52] scales_1.1.1            promises_1.2.0.1        hms_1.1.0              
## [55] ProtGenerics_1.24.0     AnnotationFilter_1.16.0 yaml_2.2.1             
## [58] sass_0.4.0              stringi_1.7.4           highr_0.9              
## [61] boot_1.3-28             zip_2.2.0               BiocParallel_1.26.2    
## [64] rlang_0.4.11            pkgconfig_2.0.3         bitops_1.0-7           
## [67] evaluate_0.14           lattice_0.20-45         htmlwidgets_1.5.4      
## [70] labeling_0.4.2          cowplot_1.1.1           tidyselect_1.1.1       
## [73] magrittr_2.0.1          R6_2.5.1                generics_0.1.0         
## [76] DelayedArray_0.18.0     DBI_1.1.1               pillar_1.6.2           
## [79] haven_2.4.3             withr_2.4.2             MsCoreUtils_1.4.0      
## [82] RCurl_1.98-1.5          modelr_0.1.8            crayon_1.4.1           
## [85] utf8_1.2.2              tzdb_0.1.2              rmarkdown_2.11         
## [88] grid_4.1.1              readxl_1.3.1            reprex_2.0.1           
## [91] digest_0.6.28           xtable_1.8-4            httpuv_1.6.3           
## [94] munsell_0.5.0           bslib_0.3.0             shinyjs_2.0.0
LS0tCnRpdGxlOiAiRHluYW1pYyBSZXBvcnQgZm9yIGRpZmZlcmVudGlhbCBhYnVuZGFuY2UgYW5hbHlzaXMgaW4gTVMtYmFzZWQgcHJvdGVvbWljcyB1c2luZyB0aGUgbXNxcm9iMmd1aSBBcHAiCmF1dGhvcjogIm1zcXJvYjJndWkiCmRhdGU6ICJEZXZlbG9wZWQgYnkgW3N0YXRPbWljc10oaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvKSIKb3V0cHV0OgogICAgaHRtbF9kb2N1bWVudDoKICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgICB0aGVtZTogY29zbW8KICAgICAgdG9jOiB0cnVlCiAgICAgIHRvY19mbG9hdDogdHJ1ZQogICAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgcGRmX2RvY3VtZW50OgogICAgICB0b2M6IHRydWUKICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCmxpbmtjb2xvcjogYmx1ZQp1cmxjb2xvcjogYmx1ZQpjaXRlY29sb3I6IGJsdWUKCi0tLQoKPGEgcmVsPSJsaWNlbnNlIiBocmVmPSJodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wIj48aW1nIGFsdD0iQ3JlYXRpdmUgQ29tbW9ucyBMaWNlbnNlIiBzdHlsZT0iYm9yZGVyLXdpZHRoOjAiIHNyYz0iaHR0cHM6Ly9pLmNyZWF0aXZlY29tbW9ucy5vcmcvbC9ieS1uYy1zYS80LjAvODh4MzEucG5nIiAvPjwvYT4KCgojIEJhY2tncm91bmQKRGVzY3JpYmUgZGF0YSBoZXJlLiAKClRoZSBmb2xsb3dpbmcgcGFja2FnZXMgYXJlIG5lZWRlZCBmb3IgdGhpcyByZXBvcnQgCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoUUZlYXR1cmVzKQpsaWJyYXJ5KG1zcXJvYjIpCmBgYAoKIyBJbnB1dCBwYXJhbWV0ZXJzIGNvcGllZCBmcm9tIG1zcXJvYjJndWkKCkluaXRpYWxpemUgYWxsIGlucHV0IHBhcmFtZXRlcnMgdXNlZCBpbiB0aGUgR1VJLiAKCiMjIElucHV0IHRhYgoKYGBge3J9CmZlYXR1cmVzRmlsZSA8LSAiZmVhdHVyZXNGaWxlLnR4dCIKYW5ub3RhdGlvbkZpbGUgPC0gImFubm90YXRpb25GaWxlLnhsc3giCmBgYAoKU3BlY2lmaWNhdGlvbiBvZiBmZWF0dXJlIGZpbGUgYW5kIGFubm90YXRpb24gZmlsZS4gTm90ZSB0aGF0IHRoZSBmZWF0dXJlIGZpbGUgYW5kIHRoZSBhbm5vdGF0aW9uIGZpbGUgYXJlIGNvcGllZCB0byB0aGUgemlwIGZpbGUgZm9yIHRoZSByZXBvcnQgYW5kIGFyZSBuYW1lZApmZWF0dXJlc0ZpbGUudHh0IGFuZCBhbm5vdGF0aW9uRmlsZS54bHN4LCByZXNwZWN0aXZlbHkuIAoKCgojIyBQcmVwcm9jZXNzaW5nIHRhYgoKYGBge3J9Cmdyb3VwQnkgPC0gIlByb3RlaW5zIgpsb2dUcmFucyA8LSBUUlVFCnJlbW92ZVJhem9yIDwtIFRSVUUKZmlsdGVyQ29sdW1ucyA8LSBjKCJSZXZlcnNlIiwgIlBvdGVudGlhbC5jb250YW1pbmFudCIpCm1pbk9ic0ZlYXQgPC0gMkwKbm9ybU1ldGhvZCA8LSAiY2VudGVyLm1lZGlhbiIKYGBgCgotIFRoZSBmZWF0dXJlcyBhcmUgZ3JvdXBlZCBieSB0aGUgdmFyaWFibGUgZ3JvdXBCeT1gciBncm91cEJ5YC4gCi0gTG9nIHRyYW5zZm9ybWF0aW9uOiBsb2dUcmFucz1gciBsb2dUcmFuc2AuCi0gUmVtb3ZlIHJhem9yIHBlcHRpZGVzOiByZW1vdmVSYXpvcj1gciByZW1vdmVSYXpvcmAKLSBGb2xsb3dpbmcgdmFyaWFibGVzIGFyZSB1c2VkIGZvciBmaWx0ZXJpbmc6IGZpbHRlckNvbHVtbnM9IGByIGZpbHRlckNvbHVtbnNgCi0gUGVwdGlkZXMgdGhhdCBhcmUgcGlja2VkIHVwIGluIGxlc3MgdGhhbiBtaW5PYnNGZWF0PWByIG1pbk9ic0ZlYXRgIGFyZSByZW1vdmVkCi0gVGhlIGZlYXR1cmVzIGFyZSBub3JtYWxpemVkIHVzaW5nIG5vcm1NZXRob2Q9YHIgbm9ybU1ldGhvZGAgCgoKCiMjIFN1bW1hcml6YXRpb24gdGFiCgpgYGB7cn0Kc3VtTWV0aG9kIDwtICJyb2J1c3QiCmBgYAoKVGhlIGZlYXR1cmVzIGFyZSBzdW1tYXJpemVkIGFjY29yZGluZyB0byB0aGUgdmFyaWFibGUgZ3JvdXBCeT1gciBncm91cEJ5YCB1c2luZyBzdW1tYXJpemF0aW9uIG1ldGhvZDogc3VtTWV0aG9kPWByIHN1bU1ldGhvZGAuIAoKCgojIyBNb2RlbCB0YWIKCmBgYHtyfQpmb3JtIDwtICJ+dHJlYXRtZW50KnRpbWUiCmRvUmlkZ2UgPC0gRkFMU0UKYGBgCgotIFRoZSBkYXRhIGFyZSBtb2RlbGxlZCB1c2luZyB0aGUgZm9sbG93aW5nIGZvcm11bGE6IGZvcm09YHIgZm9ybWAKCi0gVGhlIHBhcmFtZXRlcnMgYXJlIGVzdGltYXRlZCB1c2luZyByb2J1c3QgcmlkZ2UgcmVncmVzc2lvbiBpZiB0aGUgYXJndW1lbnQgZG9SaWRnZSBlcXVhbHMgVFJVRSwgb3RoZXJ3aXNlIHJvYnVzdCByZWdyZXNpb24gd2l0aG91dCByaWRnZSBwZW5hbGl6YXRpb24uIEhlcmUsIGRvUmlkZ2U9YHIgZG9SaWRnZWAKCiMjIEluZmVyZW5jZSB0YWIKCmBgYHtyfQpjb250cmFzdCA8LSAidHJlYXRtZW50TE4rdHJlYXRtZW50TE46dGltZTJ3PTAiCnNpZ0xldmVsIDwtIDAuMDUKYGBgCgotIFRoZSBzdGF0aXN0aWNhbCB0ZXN0IGFzc2Vzc2VzIHRoZSBudWxsIGh5cG90aGVzaXMgc3BlY2lmaWVkIHVzaW5nIHRoZSBmb2xsb3dpbmcgY29udHJhc3QgZm9yIGVhY2ggc3VtbWFyaXplZCBmZWF0dXJlczogYHIgY29udHJhc3RgLgotIFN1bW1hcml6ZWQgZmVhdHVyZXMgYXJlIHJldHVybmVkIGFzIHNpZ25pZmljYW50IHVzaW5nIHRoZSAkXGFscGhhPWByIHNpZ0xldmVsYCQgRkRSLWxldmVsLgoKCiMjIFJlcG9ydCBUYWIKCmBgYHtyfQptYXhQbG90IDwtIDEwTApgYGAKCkluIHRoZSByZXBvcnQgdGhlIG1heFBsb3Q9YHIgbWF4UGxvdGAgbW9zdCBzaWduaWZpY2FudCBmZWF0dXJlcyB3aWxsIGJlIHBsb3R0ZWQgaW4gZGV0YWlsLXBsb3RzLgoKIyBEYXRhIGltcG9ydCAKCldlIGZpcnN0IGltcG9ydCB0aGUgZGF0YSBmcm9tIEZlYXR1cmVzRmlsZS50eHQgZmlsZS4gVGhpcyBpcyB0aGUgZmlsZSBjb250YWluaW5nCnlvdXIgcmF3IEZlYXR1cmUtbGV2ZWwgaW50ZW5zaXRpZXMuIAoKVG8gaW1wb3J0IHRoZSBkYXRhIHdlIHVzZSB0aGUgYFFGZWF0dXJlc2AgcGFja2FnZS4KCmBgYHtyfQplY29scyA8LSBncmVwKCJJbnRlbnNpdHlcXC4iLCBuYW1lcyhyZWFkLmRlbGltKGZlYXR1cmVzRmlsZSkpKQoKcGUgPC0gcmVhZFFGZWF0dXJlcygKICAgIHRhYmxlID0gZmVhdHVyZXNGaWxlLCBmbmFtZXMgPSAxLCBlY29sID0gZWNvbHMsCiAgICBuYW1lID0gInJhd0ZlYXR1cmVzIiwgc2VwID0gIlx0IgopCmN1cnJlbnRBc3NheSA8LSAicmF3RmVhdHVyZXMiCnBlCmBgYAoKSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLCB3ZSBzZXQtdXAgdGhlIGNvbERhdGEgd2l0aCB0aGUgaW5mb3JtYXRpb24gb24gdGhlIGRlc2lnbi4KCi0gVGhlIGZpbGUgcGF0aCB0byB0aGUgYW5ub3RhdGlvbiBkYXRhIGlzIGByIGFubm90YXRpb25GaWxlYAoKYGBge3J9CmV4cF9hbm5vdGF0aW9uIDwtIGFzLmRhdGEuZnJhbWUodW5jbGFzcyhvcGVueGxzeDo6cmVhZC54bHN4KGFubm90YXRpb25GaWxlKSkpCgpydW5OYW1lIDwtIHdoaWNoKAogIHZhcHBseShleHBfYW5ub3RhdGlvbiwgZnVuY3Rpb24oeCkgcmV0dXJuKAogICAgaWRlbnRpY2FsKAogICAgICBzb3J0KAogICAgICAgIGFzLmNoYXJhY3RlcigKICAgICAgICAgIGNvbG5hbWVzKHBlW1sxXV0pKQogICAgICAgICksCiAgICAgIHNvcnQoCiAgICAgICAgYXMuY2hhcmFjdGVyKHgpKQogICAgICApCiAgICApLCBGVU4uVkFMVUUgPSBUUlVFKSkKaWYgKGxlbmd0aChydW5OYW1lKT4wKSBydW5OYW1lIDwtIHJ1bk5hbWVbMV0Kcm93bmFtZXMoZXhwX2Fubm90YXRpb24pIDwtIGFzLmNoYXJhY3RlcigKICBleHBfYW5ub3RhdGlvblsscnVuTmFtZV0KICApCgpleHBfYW5ub3RhdGlvbiA8LSBleHBfYW5ub3RhdGlvbltjb2xuYW1lcyhwZVtbMV1dKSxdCgpmb3IgKGogaW4gY29sbmFtZXMoZXhwX2Fubm90YXRpb24pKQogIGNvbERhdGEocGUpW1tqXV0gPC0gZXhwX2Fubm90YXRpb25bLGpdCmBgYAoKCiMgUHJlcHJvY2Vzc2luZwoKVGhpcyBzZWN0aW9uIHByZWZvcm1zIHByZXByb2Nlc3NpbmcgZm9yIHRoZSBmZWF0dXJlLiAKVGhpcyBpbmNsdWRlIAoKLSBsb2cgdHJhbnNmb3JtYXRpb24sIAotIGZpbHRlcmluZyBhbmQgCi0gc3VtbWFyaXphdGlvbiBvZiB0aGUgZGF0YS4KCiMjIExvZy10cmFuc2Zvcm1hdGlvbgoKV2UgY2FsY3VsYXRlIGhvdyBtYW55IG5vbiB6ZXJvIGludGVuc2l0aWVzIHdlIGhhdmUgcGVyIGZlYXR1cmUgYW5kIHRoaXMKd2lsbCBiZSB1c2VmdWwgZm9yIGZpbHRlcmluZy4KCmBgYHtyfQpyb3dEYXRhKHBlW1tjdXJyZW50QXNzYXldXSkkbk5vblplcm8gPC0gcm93U3Vtcyhhc3NheShwZVtbY3VycmVudEFzc2F5XV0pID4gMCkKYGBgCgoKZmVhdHVyZXMgd2l0aCB6ZXJvIGludGVuc2l0aWVzIGFyZSBtaXNzaW5nIGZlYXR1cmVzIGFuZCBzaG91bGQgYmUgcmVwcmVzZW50CndpdGggYSBgTkFgIHZhbHVlIHJhdGhlciB0aGFuIGAwYC4KYGBge3J9CnBlIDwtIHplcm9Jc05BKHBlLCBjdXJyZW50QXNzYXkpICMgY29udmVydCAwIHRvIE5BCmBgYAoKYHIgZm9ybWF0KG1lYW4oaXMubmEoYXNzYXkocGVbW2N1cnJlbnRBc3NheV1dKSkpKjEwMCxkaWdpdHM9MilgJSBvZiBhbGwgZmVhdHVyZQppbnRlbnNpdGllcyBhcmUgbWlzc2luZyBhbmQgZm9yIHNvbWUgZmVhdHVyZXMgd2UgZG8gbm90IGV2ZW4gbWVhc3VyZSBhIHNpZ25hbAppbiBhbnkgc2FtcGxlLgoKSWYgdGhlIHZhcmlhYmxlIGBsb2dUcmFuc2AgaXMgVFJVRSB0aGFuIHRoZSBsb2d0cmFuc2Zvcm1hdGlvbiBpcyBwZXJmb3JtZWQuIAoKYGBge3J9CmxvZ1RyYW5zCmBgYAoKYGBge3J9CmlmKGxvZ1RyYW5zKSB7CiAgcGUgPC0gbG9nVHJhbnNmb3JtKHBlLCBiYXNlID0gMiwgaSA9IGN1cnJlbnRBc3NheSwgbmFtZSA9ICJsb2dGZWF0dXJlcyIpCiAgY3VycmVudEFzc2F5IDwtICJsb2dGZWF0dXJlcyIKfSAKYGBgCgojIyBGaWx0ZXJpbmcKCjEuIEhhbmRsaW5nIHJhem9yIGZlYXR1cmVzCgpJbiBtYXhRdWFudCBvdXRwdXQgYSBwZXB0aWRlIGNhbiBtYXAgdG8gbXVsdGlwbGUgcHJvdGVpbnMsIGFzIGxvbmcgYXMgdGhlcmUgaXMKbm9uZSBvZiB0aGVzZSBwcm90ZWlucyBwcmVzZW50IGluIGEgc21hbGxlciBzdWJncm91cC4KCmlmIGByZW1vdmVSYXpvcmAgaXMgVFJVRS4gUmF6b3IgZmVhdHVyZXMgYXJlIHJlbW92ZWQuIAoKYGBge3J9CnJlbW92ZVJhem9yCmBgYAoKYGBge3J9CmlmIChyZW1vdmVSYXpvcikgcGVbW2N1cnJlbnRBc3NheV1dIDwtCiBwZVtbY3VycmVudEFzc2F5XV1bcm93RGF0YShwZVtbY3VycmVudEFzc2F5XV0pWyxncm91cEJ5XQogJWluJSBzbWFsbGVzdFVuaXF1ZUdyb3Vwcyhyb3dEYXRhKHBlW1tjdXJyZW50QXNzYXldXSlbLGdyb3VwQnldKSxdCmBgYAoKMi4gRmlsdGVyCgpUaGUgdmFyaWFibGUgYGZpbHRlckNvbHVtbnNgIGNvbnRhaW5zIHRoZSBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGZpbHRlciB0aGUgZGF0YS4gVGhpcyBjdXJyZW50bHkgb25seSB3b3JrcyBmb3IgTWF4cXVhbnQgaW5wdXQuIEZvciBNYXhxdWFudCB2YWx1ZXMgdGhhdCBhcmUgaW5kaWNhdGVkIHdpdGggYSAiKyIgYXJlIGZpbHRlcmVkLCBlLmcuIERlY295cyAoaW4gdGhlIGNvbHVtbiBuYW1lZCBSZXZlcnNlKSBhbmQgY29udGFtaW5hbnRzIChpbiB0aGUgY29sdW1ucyBuYW1lZCBDb250YW1pbmFudHMgb3IgUG90ZW50aWFsLmNvbnRhbWluYW50cykuIAoKYGBge3J9CmZpbHRlckNvbHVtbnMKYGBgCgpgYGB7cn0KaWYgKGxlbmd0aChmaWx0ZXJDb2x1bW5zKT4wKQogIGZvciAoaiBpbiBmaWx0ZXJDb2x1bW5zKQogIHsKICAgICAgcm93RGF0YShwZVtbY3VycmVudEFzc2F5XV0pW2lzLm5hKHJvd0RhdGEocGVbW2N1cnJlbnRBc3NheV1dKVssal0pLGpdIDwtIiIKICAgICAgcGVbW2N1cnJlbnRBc3NheV1dIDwtIHBlW1tjdXJyZW50QXNzYXldXVtyb3dEYXRhKHBlW1tjdXJyZW50QXNzYXldXSlbLGpdICE9ICIrIiwgXQogIH0KYGBgCgozLiBEcm9wIGZlYXR1cmVzIHRoYXQgd2VyZSBvbmx5IGlkZW50aWZpZWQgaW4gbGVzcyB0aGFuIGByIG1pbk9ic0ZlYXRgIHNhbXBsZXMKCgpgYGB7cn0KbWluT2JzRmVhdApgYGAKCmBgYHtyfQpwZVtbY3VycmVudEFzc2F5XV0gPC0gcGVbW2N1cnJlbnRBc3NheV1dW3Jvd0RhdGEocGVbW2N1cnJlbnRBc3NheV1dKSRuTm9uWmVybyA+PSBtaW5PYnNGZWF0LCBdCm5yb3cocGVbW2N1cnJlbnRBc3NheV1dKQpgYGAKCldlIGtlZXAgYHIgbnJvdyhwZVtbY3VycmVudEFzc2F5XV0pYCBmZWF0dXJlcyB1cG9uIGZpbHRlcmluZy4KCgojIyBOb3JtYWxpemUgdGhlIGRhdGEgCgpUaGUgZGF0YSBhcmUgbm9ybWFsaXplZCB1c2luZyB0aGUgYHIgbm9ybU1ldGhvZGAgbWV0aG9kIChJZiBub3JtTWV0aG9kIGlzICJub25lIiBubyBub3JtYWxpc2F0aW9uIGlzIHBlcmZvcm1lZCkuIAoKYGBge3J9Cm5vcm1NZXRob2QKYGBgCgpgYGB7cn0KaWYgKG5vcm1NZXRob2QgIT0gIm5vbmUiKQp7CiAgcGUgPC0gbm9ybWFsaXplKHBlLCAKICAgICAgICAgICAgICAgIGkgPSBjdXJyZW50QXNzYXksIAogICAgICAgICAgICAgICAgbmFtZSA9ICJub3JtRmVhdHVyZXMiLCAKICAgICAgICAgICAgICAgIG1ldGhvZCA9IG5vcm1NZXRob2QpCiAgY3VycmVudEFzc2F5IDwtICJub3JtRmVhdHVyZXMiCn0KYGBgCgoKIyMgRXhwbG9yZSAgbm9ybWFsaXplZCBkYXRhCgpgYGB7cn0KICBwZVtbY3VycmVudEFzc2F5XV0gJT4lIAogICAgYXNzYXkgJT4lCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgICBnYXRoZXIoc2FtcGxlLCBpbnRlbnNpdHkpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBpbnRlbnNpdHksIGdyb3VwID0gc2FtcGxlLCBjb2xvciA9IHNhbXBsZSkpICsKICAgIGdlb21fZGVuc2l0eSgpCmBgYAoKV2UgY2FuIHZpc3VhbGl6ZSBvdXIgZGF0YSB1c2luZyBhIE11bHRpIERpbWVuc2lvbmFsIFNjYWxpbmcgcGxvdCwKZWcuIGFzIHByb3ZpZGVkIGJ5IHRoZSBgbGltbWFgIHBhY2thZ2UuCgpgYGB7cn0KcGVbW2N1cnJlbnRBc3NheV1dICU+JSAKICBhc3NheSAlPiUKICBsaW1tYTo6cGxvdE1EUygpIApgYGAKClRoZSBmaXJzdCBheGlzIGluIHRoZSBwbG90IGlzIHNob3dpbmcgdGhlIGxlYWRpbmcgbG9nIGZvbGQgY2hhbmdlcwooZGlmZmVyZW5jZXMgb24gdGhlIGxvZyBzY2FsZSkgYmV0d2VlbiB0aGUgc2FtcGxlcy4KCgoKIyMgU3VtbWFyaXphdGlvbiAKClRoZSBkYXRhIGFyZSBzdW1tYXJpemVkIHVzaW5nIHRoZSBzdW1tYXJpemF0aW9uIG1ldGhvZDogYHIgc3VtTWV0aG9kYCAoaWYgc3VtTWV0aG9kIGlzICJub25lIiBubyBzdW1tYXJpemF0aW9uIGlzIHBlcmZvcm1lZCkuIAoKYGBge3J9CnN1bU1ldGhvZApgYGAKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQppZiAoc3VtTWV0aG9kIT0ibm9uZSIpIAp7CiAgaWYgKHN1bU1ldGhvZD09InJvYnVzdCIpIGZ1biA8LSBNc0NvcmVVdGlsczo6cm9idXN0U3VtbWFyeQogIGlmIChzdW1NZXRob2Q9PSJzdW0iKSBmdW4gPC0gYmFzZTo6Y29sU3VtcwogIGlmIChzdW1NZXRob2Q9PSJtZWFuIikgZnVuIDwtIGJhc2U6OmNvbE1lYW5zCiAgaWYgKHN1bU1ldGhvZD09Im1lZGlhbiIpIGZ1biA8LSBtYXRyaXhTdGF0czo6Y29sTWVkaWFucwogIGlmIChzdW1NZXRob2Q9PSJtZWRwb2xpc2giKSBmdW4gPC0gTXNDb3JlVXRpbHM6Om1lZGlhblBvbGlzaAoKICBwZSA8LSBhZ2dyZWdhdGVGZWF0dXJlcyhwZSwKICBpID0gY3VycmVudEFzc2F5LAogIGZjb2wgPSBncm91cEJ5LAogIG5hLnJtID0gVFJVRSwKICBuYW1lID0gInN1bUZlYXR1cmVzIiwKICBmdW4gPSBmdW4KICApCiAgY3VycmVudEFzc2F5IDwtICJzdW1GZWF0dXJlcyIKfQpgYGAKCgpBbiBNRFMgcGxvdCBvZiB0aGUgc3VtbWFyaXplZCBmZWF0dXJlcyBjYW4gYmUgZm91bmQgYmVsb3cuIAoKYGBge3J9CiBwbG90TURTKGFzc2F5KHBlW1tjdXJyZW50QXNzYXldXSkpCmBgYAoKCiMgRGF0YSBBbmFseXNpcwoKIyMgRXN0aW1hdGlvbgoKV2UgbW9kZWwgdGhlIHN1bW1hcml6ZWQgZmVhdHVyZSBsZXZlbCBleHByZXNzaW9uIHZhbHVlcyB1c2luZyBgbXNxcm9iYC4KQnkgZGVmYXVsdCBgbXNxcm9iMmAgZXN0aW1hdGVzIHRoZSBtb2RlbCBwYXJhbWV0ZXJzIHVzaW5nIHJvYnVzdCByZWdyZXNzaW9uLgoKV2Ugd2lsbCBtb2RlbCB0aGUgZGF0YSB1c2luZyBmb2xsb3dpbmcgZm9ybXVsYQoKYGBge3J9CmFzLmZvcm11bGEoZm9ybSkKYGBgCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KcGUgPC0gbXNxcm9iKG9iamVjdCA9IHBlLCBpID0gY3VycmVudEFzc2F5LCBmb3JtdWxhID0gYXMuZm9ybXVsYShmb3JtKSwgcmlkZ2UgPSBkb1JpZGdlKQpgYGAKCiMjIEluZmVyZW5jZQoKV2UgY2FuIGFsc28gZXhwbG9yZSB0aGUgZGVzaWduIG9mIHRoZSBtb2RlbCB0aGF0IHdlIHNwZWNpZmllZCB1c2luZyB0aGUgdGhlIHBhY2thZ2UgYEV4cGxvcmVNb2RlbE1hdHJpeGAgCgpgYGB7cn0KbGlicmFyeShFeHBsb3JlTW9kZWxNYXRyaXgpCnZpc0Rlc2lnbiA8LSBWaXN1YWxpemVEZXNpZ24oY29sRGF0YShwZSksZm9ybSkKdmlzRGVzaWduJHBsb3RsaXN0CmBgYAoKVGhlIGZvbGxvd2luZyBjb250cmFzdCB3aWxsIGJlIGFzc2Vzc2VkIGZvciBlYWNoIHN1bW1hcml6ZWQgZmVhdHVyZToKCmBgYHtyfQpjb250cmFzdApgYGAKCmBgYHtyfQpMIDwtIG1ha2VDb250cmFzdCgKICBjb250cmFzdCwgCiAgcGFyYW1ldGVyTmFtZXMgPSBjb2xuYW1lcyh2aXNEZXNpZ25bWzNdXSkKICApCnBlIDwtIGh5cG90aGVzaXNUZXN0KG9iamVjdCA9IHBlLCBpID0gY3VycmVudEFzc2F5LCBjb250cmFzdCA9IEwpCmBgYAoKVGhlIGZvbGxvd2luZyBmZWF0dXJlcyBhcmUgc2lnbmlmaWNhbnQgYXQgdGhlIGByIHNpZ0xldmVsYCBGRFItbGV2ZWwuCmBgYHtyfQpyb3dEYXRhKHBlW1tjdXJyZW50QXNzYXldXSlbLGNvbG5hbWVzKEwpXSAlPiUgCiAgYXJyYW5nZShwdmFsKSAlPiUgCiAgZmlsdGVyKGFkalB2YWwgPCBzaWdMZXZlbCkgJT4lIAogIERUOjpkYXRhdGFibGUoKSAlPiUgCiAgRFQ6OmZvcm1hdFNpZ25pZihjb2x1bW5zID0gMTo2LGRpZ2l0cz0zKQpgYGAKCgojIyBQbG90cwoKIyMjIFZvbGNhbm8tcGxvdAoKVGhlIEZEUiBpcyBjb250cm9sbGVkIGF0IHRoZSBgciBzaWdMZXZlbGAuIAoKYGBge3Isd2FybmluZz1GQUxTRX0Kc2lnTGV2ZWwKdm9sY2FubyA8LSBnZ3Bsb3Qocm93RGF0YShwZVtbY3VycmVudEFzc2F5XV0pWywgY29sbmFtZXMoTCldLAogICAgICAgICAgICAgICAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpLCBjb2xvciA9IGFkalB2YWwgPCBzaWdMZXZlbCkpICsKICBnZW9tX3BvaW50KGNleCA9IDIuNSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKyB0aGVtZV9taW5pbWFsKCkKdm9sY2FubwpgYGAKCiMjIyBIZWF0bWFwCgpXZSBmaXJzdCBzZWxlY3QgdGhlIG5hbWVzIG9mIHRoZSBzdW1tYXJpemVkIGZlYXR1cmVzIHRoYXQgd2VyZSBkZWNsYXJlZCBzaWduZmljYW50IGF0IHRoZSBGRFItbGV2ZWwgb2YgYHIgc2lnTGV2ZWxgLgpJZiB3ZSBjb3VsZCByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcyByZWxhdGVkIHRvIHRoZSBzcGVjaWZpZWQgY29udHJhc3QgZm9yIG1vcmUgdGhhbiAxIHN1bW1hcml6ZWQgZmVhdHVyZSBhIGhlYXRtYXAgd2lsbCBiZSBtYWRlIGZvciB0aGUgc2lnbmlmaWNhbnQgZmVhdHVyZXMuIAoKYGBge3J9CnNpZ0xldmVsCnNpZ05hbWVzIDwtIHJvd0RhdGEocGVbW2N1cnJlbnRBc3NheV1dKVssY29sbmFtZXMoTCldICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigiZmVhdHVyZSIpICU+JQogIGFycmFuZ2UocHZhbCkgJT4lIAogIGZpbHRlcihhZGpQdmFsPHNpZ0xldmVsKSAlPiUKICBwdWxsKGZlYXR1cmUpCmlmIChsZW5ndGgoc2lnTmFtZXMpID4xKSBoZWF0bWFwKGFzc2F5KHBlW1tjdXJyZW50QXNzYXldXSlbc2lnTmFtZXMsIF0pIGVsc2UgY2F0KCJObyBwbG90cyBhcmUgZ2VuZXJhdGVkIGJlY2F1c2UgdGhlcmUgYXJlIG5vIHNpZ25pZmljYW50IHN1bW1hcml6ZWQgZmVhdHVyZXMgYXQgdGhlIiwgc2lnTGV2ZWwsICJGRFIgbGV2ZWwiKQpgYGAKCiMjIyBEZXRhaWwgcGxvdHMKCldlIGZpcnN0IGV4dHJhY3QgdGhlIG5vcm1hbGl6ZWQgcmF3RmVhdHVyZXMgZXhwcmVzc2lvbiB2YWx1ZXMgZm9yIGEgcGFydGljdWxhciBzdW1tYXJpemVkIGZlYXR1cmUuCllvdSBzZWxlY3RlZCBgbWF4UGxvdGA9YHIgbWF4UGxvdGAgc28gIGRldGFpbCBwbG90cyBhcmUgY29uc3RydWN0ZWQgZm9yIHRoZSBgciBtYXhQbG90YCBtb3N0IHNpZ25pZmljYW50IHN1bW1hcml6ZWQgZmVhdHVyZXMgdGhhdCBhcmUgREEgYXQgdGhlIHNwZWNpZmllZCBGRFIgbGV2ZWwgb2YgYHIgc2lnTGV2ZWxgLiAKTm90ZSwgdGhhdCB5b3UgY2FuIGluY3JlYXNlIGBtYXhQbG90YCB0byBnZW5lcmF0ZSBtb3JlIHBsb3RzLiAKCgpgYGB7cn0KbWF4UGxvdApgYGAKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKaWYgKGxlbmd0aChzaWdOYW1lcykgPiBtYXhQbG90KSAKICBwbG90TmFtZXMgPC0gc2lnTmFtZXNbMTptYXhQbG90XSBlbHNlIAogICAgcGxvdE5hbWVzIDwtIHNpZ05hbWVzCmlmICgibm9ybUZlYXR1cmVzIiAlaW4lIG5hbWVzKHBlKSAmICJzdW1GZWF0dXJlcyIgJWluJSBuYW1lcyhwZSkgJiBsZW5ndGgocGxvdE5hbWVzKSA+PSAxKSBmb3IgKHByb3ROYW1lIGluIHBsb3ROYW1lcykKewpwZVBsb3QgPC0gcGVbcHJvdE5hbWUsICwgYygibm9ybUZlYXR1cmVzIiwic3VtRmVhdHVyZXMiKV0KcGVQbG90RGYgPC0gZGF0YS5mcmFtZShsb25nRm9ybWF0KHBlUGxvdCkpCnBlUGxvdERmJGFzc2F5IDwtIGZhY3RvcihwZVBsb3REZiRhc3NheSwKICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygibm9ybUZlYXR1cmVzIiwic3VtRmVhdHVyZXMiKSkKIyBwbG90dGluZwpwMSA8LSBnZ3Bsb3QoCiAgICBkYXRhID0gcGVQbG90RGYsCiAgICBhZXMoeCA9IGNvbG5hbWUsIHkgPSB2YWx1ZSwgZ3JvdXAgPSByb3duYW1lKQogICAgKSArCiAgZ2VvbV9saW5lKCkgKyAKICBnZW9tX3BvaW50KCkgKyAgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA3MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSkpICsKICBmYWNldF9ncmlkKH5hc3NheSkgKyAKICBnZ3RpdGxlKHByb3ROYW1lKSAKcHJpbnQocDEpCgojIHBsb3R0aW5nIDIKcDIgPC0gZ2dwbG90KAogICAgcGVQbG90RGYsIAogICAgYWVzKHggPSBjb2xuYW1lLCB5ID0gdmFsdWUpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkgKyAKICBnZW9tX3BvaW50KAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAuMSksCiAgICBhZXMoc2hhcGUgPSByb3duYW1lKSkgKwogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXMgPSAxOm5yb3cocGVQbG90RGYpKSArCiAgbGFicyh0aXRsZSA9IHByb3ROYW1lLCB4ID0gInNhbXBsZSIsIHkgPSAiZmVhdHVyZSBpbnRlbnNpdHkgKGxvZzIpIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNzAsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUpKSArCiAgZmFjZXRfZ3JpZCh+YXNzYXkpICsKICBnZ3RpdGxlKHByb3ROYW1lKSAKcHJpbnQocDIpCn0gZWxzZSBjYXQoIk5vIHBsb3RzIGFyZSBnZW5lcmF0ZWQgYmVjYXVzZSB0aGVyZSBhcmUgbm8gc2lnbmlmaWNhbnQgc3VtbWFyaXplZCBmZWF0dXJlcyBhdCB0aGUiLCBzaWdMZXZlbCwgIkZEUiBsZXZlbCIpCmBgYAoKCgojIFNlc3Npb24gSW5mbwoKV2l0aCByZXNwZWN0IHRvIHJlcHJvZHVjaWJpbGl0eSwgaXQgaXMgaGlnaGx5IHJlY29tbWVuZGVkIHRvIGluY2x1ZGUgYSBzZXNzaW9uIGluZm8gaW4geW91ciBzY3JpcHQgc28gdGhhdCByZWFkZXJzIG9mIHlvdXIgb3V0cHV0IGNhbiBzZWUgeW91ciBwYXJ0aWN1bGFyIHNldHVwIG9mIFIuIAoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCg==